﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BFlatCompile.Nodes
{
	public class Loop : Executable
	{
		private enum LoopType
		{
			For,
			While,
			DoWhile
		}

		public Executable[] Initialization { get; private set; }
		public Executable[] Code { get; private set; }
		public Executable[] Step { get; private set; }
		public Expression Condition { get; private set; }
		public Token ConditionOpenParenToken { get; private set; }

		private static readonly ICodeContainer[] NO_CODE = new ICodeContainer[0];
		private IList<ICodeContainer> AllCode
		{
			get
			{
				List<ICodeContainer> codes = new List<ICodeContainer>();

				codes.AddRange(this.Initialization ?? NO_CODE);
				codes.AddRange(this.Code ?? NO_CODE);
				codes.AddRange(this.Step ?? NO_CODE);
				if (this.Condition != null) { codes.Add(this.Condition); }
				return codes;
			}
		}

		private LoopType type;

		public bool IsForLoop { get { return this.type == LoopType.For; } }
		public bool IsWhileLoop { get { return this.type == LoopType.While; } }
		public bool IsDoWhileLoop { get { return this.type == LoopType.DoWhile; } }

		private Loop(Expression condition, Executable[] initialization, Executable[] step, Executable[] code, LoopType type, Token conditionOpenParenToken)
		{
			this.type = type;
			this.Condition = condition;
			this.ConditionOpenParenToken = conditionOpenParenToken;
			this.Initialization = initialization ?? Executable.Noop;
			this.Code = code;
			this.Step = step;
		}

		public static Executable ParseWhile(Tokens tokens)
		{
			tokens.PopExpected("while");
			tokens.PopExpected("(");
			Token token = tokens.PreviousToken();
			Expression condition = Expression.Parse(tokens);
			tokens.PopExpected(")");
			Executable[] code = CodeBlock.Parse(tokens, false);

			return new Loop(condition, Executable.Noop, Executable.Noop, code, LoopType.While, token);
		}

		public static Executable ParseDoWhile(Tokens tokens)
		{
			tokens.PopExpected("do");
			Executable[] code = CodeBlock.Parse(tokens, true);
			tokens.PopExpected("while");
			tokens.PopExpected("(");
			Token token = tokens.PreviousToken();
			Expression condition = Expression.Parse(tokens);
			tokens.PopExpected(")");
			tokens.PopExpected(";");

			return new Loop(condition, Executable.Noop, Executable.Noop, code, LoopType.DoWhile, token);
		}

		public static Executable ParseFor(Tokens tokens)
		{
			tokens.PopExpected("for");
			tokens.PopExpected("(");

			List<Executable> init = new List<Executable>();

			while (tokens.Peek() != ";")
			{
				if (init.Count > 0)
				{
					tokens.PopExpected(",", ";");
				}

				init.Add(Executable.Parse(tokens, false, false));
			}

			tokens.PopExpected(";");
			// TODO: after adding the implementation for a blank condition implicitly means "true", I realized that the init and step parts need to be checked for blank as well.
			Expression condition = BooleanConstant.True;
			Token token = null;
			if (tokens.Peek() != ";")
			{
				token = tokens.PeekToken(); // just point to the beginning of the expression.
				condition = Expression.Parse(tokens);
			}

			tokens.PopExpected(";");

			List<Executable> step = new List<Executable>();

			while (tokens.Peek() != ")")
			{
				if (step.Count > 0)
				{
					tokens.PopExpected(",", ")");
				}

				step.Add(Executable.Parse(tokens, false, false));
			}

			tokens.PopExpected(")");

			Executable[] code = CodeBlock.Parse(tokens, false);

			return new Loop(condition, init.ToArray(), step.ToArray(), code, LoopType.For, token);
		}

		public override void ResolveCode(Class c, Dictionary<object, object> parents)
		{
			foreach (ICodeContainer code in this.AllCode)
			{
				code.ResolveCode(c, parents);
			}
		}

		public override void ResolveClassToThis(Class c)
		{
			foreach (ICodeContainer code in this.AllCode)
			{
				code.ResolveClassToThis(c);
			}
		}
	}
}
